// copyright (c) openFrameworks team 2010-2023
// copyright (c) Damian Stewart 2007-2009
#include "ofUtils.h"
#include "ofxOscSender.h"

//--------------------------------------------------------------
ofxOscSender::~ofxOscSender() {
	clear();
}

//--------------------------------------------------------------
ofxOscSender::ofxOscSender(const ofxOscSender & mom) {
	copy(mom);
}

//--------------------------------------------------------------
ofxOscSender & ofxOscSender::operator=(const ofxOscSender & mom) {
	return copy(mom);
}

//--------------------------------------------------------------
ofxOscSender & ofxOscSender::copy(const ofxOscSender & other) {
	if (this == &other) return *this;
	settings = other.settings;
	if (other.sendSocket) {
		setup(settings);
	}
	return *this;
}

//--------------------------------------------------------------
bool ofxOscSender::setup(const std::string & host, int port, bool silent) {
	settings.host = host;
	settings.port = port;
	settings.silent = silent;
	return setup(settings);
}

//--------------------------------------------------------------
bool ofxOscSender::setup(const ofxOscSenderSettings & settings) {
	// manually set larger buffer size instead of oscpack per-message size
	if (osc::UdpSocket::GetUdpBufferSize() == 0) {
		osc::UdpSocket::SetUdpBufferSize(65535);
	}

	this->settings = settings;

	// check for empty host
	if (settings.host == "") {
		ofLogError("ofxOscSender") << "couldn't create sender to empty host";
		return false;
	}

	// create socket
	osc::UdpTransmitSocket * socket = nullptr;
	try {
		osc::IpEndpointName name = osc::IpEndpointName(settings.host.c_str(), settings.port);
		if (!name.address) {
			ofLogError("ofxOscSender") << "bad host? " << settings.host;
			return false;
		}
		socket = new osc::UdpTransmitSocket(name, settings.broadcast);
		sendSocket.reset(socket);
	} catch (std::exception & e) {
		std::string what = e.what();
		// strip endline as ofLogError already adds one
		if (!what.empty() && what.back() == '\n') {
			what = what.substr(0, what.size() - 1);
		}
		ofLogError("ofxOscSender") << "couldn't create sender to "
								   << settings.host << " on port "
								   << settings.port << ": " << what;
		if (socket != nullptr) {
			delete socket;
			socket = nullptr;
		}
		sendSocket.reset();
		return false;
	}
	return true;
}

//--------------------------------------------------------------
void ofxOscSender::clear() {
	sendSocket.reset();
}

//--------------------------------------------------------------
bool ofxOscSender::isReady() const {
	return sendSocket != nullptr;
}

//--------------------------------------------------------------
bool ofxOscSender::sendBundle(const ofxOscBundle & bundle) {
	if (!isReady()) {
		if (!settings.silent) {
			ofLogError("ofxOscSender") << "trying to send with empty socket";
		}
		return false;
	}

	// setting this much larger as it gets trimmed down to the size its using before being sent.
	// TODO: much better if we could make this dynamic? Maybe have ofxOscBundle return its size?
	static const int OUTPUT_BUFFER_SIZE = 327680;
	char buffer[OUTPUT_BUFFER_SIZE];
	osc::OutboundPacketStream p(buffer, OUTPUT_BUFFER_SIZE);

	// serialise the bundle and send
	appendBundle(bundle, p);
	sendSocket->Send(p.Data(), p.Size());
	return true;
}

//--------------------------------------------------------------
bool ofxOscSender::sendMessage(const ofxOscMessage & message, bool wrapInBundle) {
	if (!isReady()) {
		if (!settings.silent) {
			ofLogError("ofxOscSender") << "trying to send with empty socket";
		}
		return false;
	}

	// setting this much larger as it gets trimmed down to the size its using before being sent.
	// TODO: much better if we could make this dynamic? Maybe have ofxOscMessage return its size?
	static const int OUTPUT_BUFFER_SIZE = 327680;
	char buffer[OUTPUT_BUFFER_SIZE];
	osc::OutboundPacketStream p(buffer, OUTPUT_BUFFER_SIZE);

	// serialise the message and send
	if (wrapInBundle) {
		p << osc::BeginBundleImmediate;
	}
	appendMessage(message, p);
	if (wrapInBundle) {
		p << osc::EndBundle;
	}
	sendSocket->Send(p.Data(), p.Size());
	return true;
}

//--------------------------------------------------------------
bool ofxOscSender::sendParameter(const ofAbstractParameter & parameter) {
	if (parameter.type() == typeid(ofParameterGroup).name()) {
		std::string address = "/";
		const std::vector<std::string> hierarchy = parameter.getGroupHierarchyNames();
		for (int i = 0; i < (int)hierarchy.size() - 1; i++) {
			address += hierarchy[i] + "/";
		}
		ofxOscBundle bundle;
		appendParameter(bundle, parameter, address);
		sendBundle(bundle);
	} else {
		std::string address = "";
		const std::vector<std::string> hierarchy = parameter.getGroupHierarchyNames();
		for (int i = 0; i < (int)hierarchy.size() - 1; i++) {
			address += "/" + hierarchy[i];
		}
		if (address.length()) {
			address += "/";
		}
		ofxOscMessage msg;
		appendParameter(msg, parameter, address);
		sendMessage(msg, false);
	}
	return true;
}

//--------------------------------------------------------------
std::string ofxOscSender::getHost() const {
	return settings.host;
}

//--------------------------------------------------------------
int ofxOscSender::getPort() const {
	return settings.port;
}

//--------------------------------------------------------------
const ofxOscSenderSettings & ofxOscSender::getSettings() const {
	return settings;
}

// PRIVATE
//--------------------------------------------------------------
void ofxOscSender::appendBundle(const ofxOscBundle & bundle, osc::OutboundPacketStream & p) {
	// recursively serialise the bundle
	p << osc::BeginBundleImmediate;
	for (int i = 0; i < bundle.getBundleCount(); i++) {
		appendBundle(bundle.getBundleAt(i), p);
	}
	for (int i = 0; i < bundle.getMessageCount(); i++) {
		appendMessage(bundle.getMessageAt(i), p);
	}
	p << osc::EndBundle;
}

//--------------------------------------------------------------
void ofxOscSender::appendMessage(const ofxOscMessage & message, osc::OutboundPacketStream & p) {
	p << osc::BeginMessage(message.getAddress().c_str());
	for (size_t i = 0; i < message.getNumArgs(); ++i) {
		switch (message.getArgType(i)) {
		case OFXOSC_TYPE_INT32:
			p << message.getArgAsInt32(i);
			break;
		case OFXOSC_TYPE_INT64:
			p << (osc::int64)message.getArgAsInt64(i);
			break;
		case OFXOSC_TYPE_FLOAT:
			p << message.getArgAsFloat(i);
			break;
		case OFXOSC_TYPE_DOUBLE:
			p << message.getArgAsDouble(i);
			break;
		case OFXOSC_TYPE_STRING:
			p << message.getArgAsString(i).c_str();
			break;
		case OFXOSC_TYPE_SYMBOL:
			p << osc::Symbol(message.getArgAsString(i).c_str());
			break;
		case OFXOSC_TYPE_CHAR:
			p << message.getArgAsChar(i);
			break;
		case OFXOSC_TYPE_MIDI_MESSAGE:
			p << osc::MidiMessage(message.getArgAsMidiMessage(i));
			break;
		case OFXOSC_TYPE_TRUE:
		case OFXOSC_TYPE_FALSE:
			p << message.getArgAsBool(i);
			break;
		case OFXOSC_TYPE_NONE:
			p << osc::NilType();
			break;
		case OFXOSC_TYPE_TRIGGER:
			p << osc::InfinitumType();
			break;
		case OFXOSC_TYPE_TIMETAG:
			p << osc::TimeTag(message.getArgAsTimetag(i));
			break;
		case OFXOSC_TYPE_RGBA_COLOR:
			p << osc::RgbaColor(message.getArgAsRgbaColor(i));
			break;
		case OFXOSC_TYPE_BLOB: {
			ofBuffer buff = message.getArgAsBlob(i);
			p << osc::Blob(buff.getData(), (unsigned long)buff.size());
			break;
		}
		default:
			ofLogError("ofxOscSender") << "appendMessage(): bad argument type "
									   << message.getArgType(i) << " '" << (char)message.getArgType(i) << "'";
			break;
		}
	}
	p << osc::EndMessage;
}

//--------------------------------------------------------------
void ofxOscSender::appendParameter(ofxOscBundle & _bundle, const ofAbstractParameter & parameter, const std::string & address) {
	if (parameter.type() == typeid(ofParameterGroup).name()) {
		ofxOscBundle bundle;
		const ofParameterGroup & group = static_cast<const ofParameterGroup &>(parameter);
		for (std::size_t i = 0; i < group.size(); i++) {
			const ofAbstractParameter & p = group[i];
			if (p.isSerializable()) {
				appendParameter(bundle, p, address + group.getEscapedName() + "/");
			}
		}
		_bundle.addBundle(bundle);
	} else {
		if (parameter.isSerializable()) {
			ofxOscMessage msg;
			appendParameter(msg, parameter, address);
			_bundle.addMessage(msg);
		}
	}
}

//--------------------------------------------------------------
void ofxOscSender::appendParameter(ofxOscMessage & msg, const ofAbstractParameter & parameter, const std::string & address) {
	msg.setAddress(address + parameter.getEscapedName());

	if (parameter.isOfType<int>()) {
		msg.addIntArg(parameter.cast<int>());
	} else if (parameter.isOfType<float>()) {
		msg.addFloatArg(parameter.cast<float>());
	} else if (parameter.isOfType<double>()) {
		msg.addDoubleArg(parameter.cast<double>());
	} else if (parameter.isOfType<bool>()) {
		msg.addBoolArg(parameter.cast<bool>());
	} else if (parameter.isOfType<void>()) {
		msg.addTriggerArg();
	} else {
		msg.addStringArg(parameter.toString());
	}
}

// friend functions
//--------------------------------------------------------------
std::ostream & operator<<(std::ostream & os, const ofxOscSender & sender) {
	os << sender.getHost() << " " << sender.getPort();
	return os;
}
